"""
Import resolution and dependency tracking for Python code.

Resolves import statements, tracks module dependencies, and builds
dependency graphs for Python projects.
"""

import ast
import sys
import os
import importlib.util
from typing import Dict, List, Any, Optional, Set, Tuple
from pathlib import Path
import pkgutil

from ...core.error_handler import AnalysisError, log_info


class ImportResolver:
    """Resolves Python imports and tracks dependencies."""
    
    def __init__(self):
        """Initialize the import resolver."""
        self.stdlib_modules = self._get_stdlib_modules()
        self.module_cache = {}
        self.dependency_graph = {}
    
    def _get_stdlib_modules(self) -> Set[str]:
        """Get set of standard library module names."""
        stdlib_modules = set()
        
        # Add known standard library modules
        stdlib_modules.update([
            'os', 'sys', 'json', 'ast', 'typing', 'pathlib', 'collections',
            'itertools', 'functools', 'operator', 'datetime', 'time', 're',
            'math', 'random', 'string', 'io', 'logging', 'threading',
            'multiprocessing', 'subprocess', 'urllib', 'http', 'email',
            'csv', 'sqlite3', 'pickle', 'base64', 'hashlib', 'uuid',
            'tempfile', 'shutil', 'glob', 'fnmatch', 'argparse', 'configparser'
        ])
        
        # Add modules from sys.builtin_module_names
        stdlib_modules.update(sys.builtin_module_names)
        
        # Try to get additional stdlib modules
        try:
            import stdlib_list
            stdlib_modules.update(stdlib_list.stdlib_list())
        except ImportError:
            # Fallback: scan standard library paths
            for path in sys.path:
                if 'site-packages' not in path and path:
                    try:
                        for module_info in pkgutil.iter_modules([path]):
                            stdlib_modules.add(module_info.name)
                    except (OSError, ImportError):
                        continue
        
        return stdlib_modules
    
    def extract_imports(self, ast_tree: ast.AST) -> List[Dict[str, Any]]:
        """Extract all import statements from an AST."""
        imports = []
        
        for node in ast.walk(ast_tree):
            if isinstance(node, ast.Import):
                for alias in node.names:
                    import_info = {
                        'type': 'import',
                        'module': alias.name,
                        'name': alias.asname if alias.asname else alias.name,
                        'alias': alias.asname,
                        'level': 0,
                        'line': node.lineno,
                        'col': node.col_offset
                    }
                    imports.append(import_info)
            
            elif isinstance(node, ast.ImportFrom):
                module = node.module if node.module else ''
                level = node.level
                
                for alias in node.names:
                    import_info = {
                        'type': 'from_import',
                        'module': module,
                        'name': alias.name,
                        'alias': alias.asname,
                        'level': level,
                        'line': node.lineno,
                        'col': node.col_offset
                    }
                    imports.append(import_info)
        
        return imports
    
    def resolve_imports(self, imports: List[Dict[str, Any]], 
                       base_path: Path) -> List[Dict[str, Any]]:
        """Resolve import statements to actual modules and classify them."""
        resolved_imports = []
        
        for import_info in imports:
            resolved = self._resolve_single_import(import_info, base_path)
            resolved_imports.append(resolved)
        
        return resolved_imports
    
    def _resolve_single_import(self, import_info: Dict[str, Any], 
                              base_path: Path) -> Dict[str, Any]:
        """Resolve a single import statement."""
        resolved = import_info.copy()
        module_name = import_info['module']
        
        # Handle relative imports
        if import_info.get('level', 0) > 0:
            resolved_module = self._resolve_relative_import(
                module_name, import_info['level'], base_path
            )
            resolved['resolved_module'] = resolved_module
            resolved['is_relative'] = True
        else:
            resolved['is_relative'] = False
        
        # Classify import type
        resolved['is_stdlib'] = self._is_stdlib_module(module_name)
        resolved['is_third_party'] = self._is_third_party_module(module_name, base_path)
        resolved['is_local'] = not resolved['is_stdlib'] and not resolved['is_third_party']
        
        # Try to resolve module path
        module_path = self._find_module_path(module_name, base_path)
        if module_path:
            resolved['module_path'] = str(module_path)
            resolved['resolved'] = True
        else:
            resolved['resolved'] = False
        
        return resolved
    
    def _resolve_relative_import(self, module_name: str, level: int, 
                                base_path: Path) -> str:
        """Resolve relative import to absolute module name."""
        # Calculate the package path based on level
        current_path = base_path
        for _ in range(level):
            current_path = current_path.parent
        
        # Build the absolute module name
        package_parts = []
        temp_path = current_path
        
        # Find the package root (directory with __init__.py)
        while temp_path != temp_path.parent:
            if (temp_path / '__init__.py').exists():
                package_parts.insert(0, temp_path.name)
                temp_path = temp_path.parent
            else:
                break
        
        if module_name:
            package_parts.append(module_name)
        
        return '.'.join(package_parts)
    
    def _is_stdlib_module(self, module_name: str) -> bool:
        """Check if module is part of standard library."""
        # Get the top-level module name
        top_level = module_name.split('.')[0]
        return top_level in self.stdlib_modules
    
    def _is_third_party_module(self, module_name: str, base_path: Path) -> bool:
        """Check if module is a third-party package."""
        # Try to find in site-packages
        try:
            spec = importlib.util.find_spec(module_name)
            if spec and spec.origin:
                origin_path = Path(spec.origin)
                return 'site-packages' in str(origin_path)
        except (ImportError, ValueError, AttributeError):
            pass
        
        return False
    
    def _find_module_path(self, module_name: str, base_path: Path) -> Optional[Path]:
        """Find the actual file path of a module."""
        # Try different strategies to find the module
        
        # Strategy 1: Use importlib
        try:
            spec = importlib.util.find_spec(module_name)
            if spec and spec.origin:
                return Path(spec.origin)
        except (ImportError, ValueError, AttributeError):
            pass
        
        # Strategy 2: Look for local modules
        module_parts = module_name.split('.')
        
        # Start from base path and work outwards
        search_paths = [base_path]
        current = base_path
        while current != current.parent:
            search_paths.append(current)
            current = current.parent
        
        for search_path in search_paths:
            # Try as a file
            module_file = search_path / f"{module_parts[0]}.py"
            if module_file.exists():
                return module_file
            
            # Try as a package
            module_dir = search_path / module_parts[0]
            if module_dir.is_dir():
                init_file = module_dir / '__init__.py'
                if init_file.exists():
                    return init_file
                
                # Look for specific submodule
                if len(module_parts) > 1:
                    submodule_path = module_dir
                    for part in module_parts[1:]:
                        submodule_path = submodule_path / part
                        if (submodule_path.parent / f"{part}.py").exists():
                            return submodule_path.parent / f"{part}.py"
                        elif submodule_path.is_dir() and (submodule_path / '__init__.py').exists():
                            continue
                        else:
                            break
        
        return None
    
    def build_dependency_graph(self, resolved_imports: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Build a dependency graph from resolved imports."""
        graph = {
            'nodes': [],
            'edges': [],
            'statistics': {
                'total_imports': len(resolved_imports),
                'stdlib_count': 0,
                'third_party_count': 0,
                'local_count': 0,
                'unresolved_count': 0
            }
        }
        
        # Track unique modules
        modules = {}
        
        for import_info in resolved_imports:
            module_name = import_info['module']
            
            # Create or update module node
            if module_name not in modules:
                modules[module_name] = {
                    'name': module_name,
                    'type': self._get_module_type(import_info),
                    'resolved': import_info.get('resolved', False),
                    'path': import_info.get('module_path'),
                    'imports': []
                }
            
            # Add import details
            modules[module_name]['imports'].append({
                'name': import_info['name'],
                'alias': import_info.get('alias'),
                'type': import_info['type'],
                'line': import_info['line']
            })
        
        # Convert to graph format
        graph['nodes'] = list(modules.values())
        
        # Build edges (dependencies between modules)
        for node in graph['nodes']:
            for other_node in graph['nodes']:
                if node['name'] != other_node['name']:
                    # Check if there's a dependency
                    if self._has_dependency(node, other_node):
                        graph['edges'].append({
                            'from': node['name'],
                            'to': other_node['name'],
                            'type': 'imports'
                        })
        
        # Calculate statistics
        for node in graph['nodes']:
            node_type = node['type']
            if node_type == 'stdlib':
                graph['statistics']['stdlib_count'] += 1
            elif node_type == 'third_party':
                graph['statistics']['third_party_count'] += 1
            elif node_type == 'local':
                graph['statistics']['local_count'] += 1
            
            if not node['resolved']:
                graph['statistics']['unresolved_count'] += 1
        
        return graph
    
    def _get_module_type(self, import_info: Dict[str, Any]) -> str:
        """Get the type of module (stdlib, third_party, local)."""
        if import_info.get('is_stdlib'):
            return 'stdlib'
        elif import_info.get('is_third_party'):
            return 'third_party'
        elif import_info.get('is_local'):
            return 'local'
        else:
            return 'unknown'
    
    def _has_dependency(self, node1: Dict[str, Any], node2: Dict[str, Any]) -> bool:
        """Check if node1 depends on node2."""
        # This is a simplified check - in practice, we'd need to analyze
        # the actual usage patterns in the code
        return False  # Placeholder
    
    def analyze_circular_dependencies(self, dependency_graph: Dict[str, Any]) -> List[List[str]]:
        """Detect circular dependencies in the import graph."""
        edges = dependency_graph['edges']
        
        # Build adjacency list
        adj_list = {}
        for edge in edges:
            from_node = edge['from']
            to_node = edge['to']
            
            if from_node not in adj_list:
                adj_list[from_node] = []
            adj_list[from_node].append(to_node)
        
        # Find strongly connected components using Tarjan's algorithm
        cycles = []
        visited = set()
        rec_stack = set()
        
        def dfs(node, path):
            if node in rec_stack:
                # Found a cycle
                cycle_start = path.index(node)
                cycle = path[cycle_start:] + [node]
                cycles.append(cycle)
                return
            
            if node in visited:
                return
            
            visited.add(node)
            rec_stack.add(node)
            path.append(node)
            
            for neighbor in adj_list.get(node, []):
                dfs(neighbor, path.copy())
            
            rec_stack.remove(node)
        
        # Check each node
        for node in adj_list:
            if node not in visited:
                dfs(node, [])
        
        return cycles